@theseus.run/jsx-md 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -204
- package/dist/index-8tdwjkh9.js +11 -0
- package/dist/index-8tdwjkh9.js.map +10 -0
- package/dist/index.js +435 -0
- package/dist/index.js.map +13 -0
- package/dist/jsx-dev-runtime.js +13 -0
- package/dist/jsx-dev-runtime.js.map +9 -0
- package/dist/jsx-runtime.js +13 -0
- package/dist/jsx-runtime.js.map +9 -0
- package/package.json +23 -8
- package/src/context.ts +47 -4
- package/src/escape.ts +69 -11
- package/src/index.ts +5 -1
- package/src/jsx-runtime.ts +36 -21
- package/src/primitives.tsx +140 -74
- package/src/render.ts +54 -19
- package/src/_render-registry.ts +0 -16
package/README.md
CHANGED
|
@@ -1,81 +1,14 @@
|
|
|
1
1
|
# `@theseus.run/jsx-md`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
JSX runtime that outputs Markdown strings — typed props, Context API, XML intrinsics, zero runtime dependencies.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
→ [Why this exists](https://romanonthego.dev/blog/jsx-that-outputs-markdown)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
const instructions = `
|
|
9
|
-
You are a code reviewer.
|
|
7
|
+
## Prior art
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
[dbartholomae/jsx-md](https://github.com/dbartholomae/jsx-md) (2019, inactive) and [eyelly-wu/jsx-to-md](https://github.com/eyelly-wu/jsx-to-md) are built for documentation generation — READMEs, changelogs. They work well for that. `dbartholomae/jsx-md` predates `jsxImportSource` and uses file-level pragma comments; its `render()` returns a Promise.
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
${firstPerson
|
|
15
|
-
? '- I always verify output before claiming done.'
|
|
16
|
-
: '- The agent must verify output before claiming done.'}
|
|
17
|
-
`
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
No syntax highlighting for the Markdown inside the string. No types on the structure. A variant is a new file. Refactoring is grep-and-pray.
|
|
21
|
-
|
|
22
|
-
Nested lists make it worse. Markdown requires exact indentation — two spaces per level. Template strings force you to hardcode that spacing:
|
|
23
|
-
|
|
24
|
-
```typescript
|
|
25
|
-
const instructions = `
|
|
26
|
-
## Rules
|
|
27
|
-
|
|
28
|
-
- Outer rule
|
|
29
|
-
- Nested rule: two hardcoded spaces
|
|
30
|
-
- Deeper: four hardcoded spaces
|
|
31
|
-
${condition ? ' - Conditional nested item' : ''}
|
|
32
|
-
`
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
A false conditional leaves a blank bullet. The indentation is load-bearing and invisible. `DepthContext` in `@theseus.run/jsx-md` tracks nesting depth automatically — you write `<Ul>` inside `<Li>` and the renderer handles the spaces.
|
|
36
|
-
|
|
37
|
-
This isn't an edge case. [oh-my-openagent](https://github.com/code-yeongyu/oh-my-openagent) — 39.8k stars, serious harness work — stores instructions the same way:
|
|
38
|
-
|
|
39
|
-
```typescript
|
|
40
|
-
export const PROMETHEUS_HIGH_ACCURACY_MODE = `# PHASE 3: PLAN GENERATION
|
|
41
|
-
## High Accuracy Mode - MANDATORY LOOP
|
|
42
|
-
\`\`\`typescript
|
|
43
|
-
while (true) {
|
|
44
|
-
const result = task(subagent_type="momus", ...)
|
|
45
|
-
if (result.verdict === "OKAY") break
|
|
46
|
-
}
|
|
47
|
-
\`\`\`
|
|
48
|
-
...` // 62 more lines of escaped template string
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
The escaped backticks are the tell — not a bad solution, it's the only solution the format offers. No composability, no reuse, no tooling support.
|
|
52
|
-
|
|
53
|
-
The web dev world solved "structured text with conditionals and composable fragments" ten years ago. JSX is a transform spec, not a React dependency — `jsxImportSource` lets you point it at any runtime. `@theseus.run/jsx-md` is a runtime that outputs Markdown:
|
|
54
|
-
|
|
55
|
-
```tsx
|
|
56
|
-
const ReviewerInstructions = ({ harness, firstPerson }: Props) => (
|
|
57
|
-
<>
|
|
58
|
-
<P>You are a code reviewer.</P>
|
|
59
|
-
{harness === 'opencode' && (
|
|
60
|
-
<HtmlComment>use task() for subtasks</HtmlComment>
|
|
61
|
-
)}
|
|
62
|
-
<H2>Traits</H2>
|
|
63
|
-
<Ul>
|
|
64
|
-
<Li>
|
|
65
|
-
{firstPerson
|
|
66
|
-
? 'I always verify output before claiming done.'
|
|
67
|
-
: 'The agent must verify output before claiming done.'}
|
|
68
|
-
</Li>
|
|
69
|
-
</Ul>
|
|
70
|
-
</>
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
render(<ReviewerInstructions harness="opencode" firstPerson={true} />)
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
No escaped backticks. Syntax highlighting in your editor. Conditionals are JSX expressions. Variants are props. Shared fragments are components.
|
|
77
|
-
|
|
78
|
-
`render()` returns a plain string — no virtual DOM, no hydration, no React runtime anywhere in the chain.
|
|
11
|
+
`@theseus.run/jsx-md` targets agent instructions assembled at call time: `render()` is synchronous, Context API ships with it, and any lowercase tag is an XML intrinsic.
|
|
79
12
|
|
|
80
13
|
---
|
|
81
14
|
|
|
@@ -83,9 +16,17 @@ No escaped backticks. Syntax highlighting in your editor. Conditionals are JSX e
|
|
|
83
16
|
|
|
84
17
|
```bash
|
|
85
18
|
bun add @theseus.run/jsx-md
|
|
19
|
+
# npm install @theseus.run/jsx-md
|
|
20
|
+
# pnpm add @theseus.run/jsx-md
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**AI coding agent skill** (OpenCode, Cursor, Copilot, Claude Code):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx skills add https://github.com/theseus-run/theseus/tree/master/packages/jsx-md
|
|
86
27
|
```
|
|
87
28
|
|
|
88
|
-
|
|
29
|
+
`tsconfig.json`:
|
|
89
30
|
|
|
90
31
|
```json
|
|
91
32
|
{
|
|
@@ -96,84 +37,71 @@ Then in `tsconfig.json`:
|
|
|
96
37
|
}
|
|
97
38
|
```
|
|
98
39
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
## What you're getting
|
|
102
|
-
|
|
103
|
-
**Zero runtime dependencies.** The package ships TypeScript source with no third-party imports. Nothing gets added to your bundle.
|
|
104
|
-
|
|
105
|
-
**Bun-first.** Ships TypeScript source directly, no compiled output. Works transparently in Bun: install, add two tsconfig lines, write TSX. The trade-off is real: vanilla Node.js without a bundler won't run `.ts` files from `node_modules`. If your stack is Vite, tsup, or esbuild, those handle it. If you're running bare Node, v0.1.0 doesn't have a solution for you yet.
|
|
106
|
-
|
|
107
|
-
**JSX without React.** When you write `<H2>Title</H2>`, the transform calls `jsx(H2, { children: "Title" })`. `H2` is a plain function — takes props, returns a string. No virtual DOM, no reconciler, no fiber, no hooks. `render()` walks the VNode tree synchronously and concatenates. The output is deterministic: same input, same string, every time. You can test it with `expect(render(<MyPrompt />)).toMatchSnapshot()`.
|
|
108
|
-
|
|
109
|
-
**TypeScript-first.** All components and their props are typed. `render()` accepts `VNode`, returns `string`. Wrong usage is a compile error, not a runtime surprise.
|
|
110
|
-
|
|
111
|
-
**String children are verbatim.** `render()` passes raw string values through without escaping. `<P>{"<b>text</b>"}</P>` outputs `<b>text</b>` — no transformation. For LLM prompts this is the right default; Markdown renderers handle the rest.
|
|
112
|
-
|
|
113
|
-
**XML-structured prompts, zero config.** Any lowercase JSX tag renders as an XML block — `<context>`, `<instructions>`, `<example index={1}>`. Anthropic's prompt engineering guide recommends this structure for Claude agents. Attributes are typed and serialized automatically; empty tags self-close. No imports, no registration — it's built into the JSX intrinsics catch-all.
|
|
40
|
+
Ships TypeScript source and compiled ESM output with sourcemaps. Bun resolves the TypeScript source directly. Node.js (≥18) and bundlers (Vite, tsup, esbuild) use the compiled output — no configuration needed, the `exports` map handles it transparently.
|
|
114
41
|
|
|
115
42
|
---
|
|
116
43
|
|
|
117
44
|
## Usage
|
|
118
45
|
|
|
119
46
|
```tsx
|
|
120
|
-
// system-prompt.tsx
|
|
121
47
|
import { render, H2, P, Ul, Li, Bold, Code } from "@theseus.run/jsx-md";
|
|
122
48
|
|
|
123
|
-
const
|
|
49
|
+
const ReviewerPrompt = ({ repo }: { repo: string }) => (
|
|
124
50
|
<>
|
|
125
51
|
<H2>Role</H2>
|
|
126
|
-
<P>
|
|
127
|
-
You are a precise code reviewer. Your job is to find bugs, not suggest
|
|
128
|
-
style changes.
|
|
129
|
-
</P>
|
|
52
|
+
<P>You are a precise code reviewer. Find bugs, not style issues.</P>
|
|
130
53
|
|
|
131
54
|
<H2>Rules</H2>
|
|
132
55
|
<Ul>
|
|
133
|
-
<Li>
|
|
134
|
-
Flag <Bold>P0</Bold> issues immediately — do not bury them.
|
|
135
|
-
</Li>
|
|
136
|
-
<Li>
|
|
137
|
-
Use <Code>inline code</Code> when referencing identifiers.
|
|
138
|
-
</Li>
|
|
56
|
+
<Li>Flag <Bold>P0</Bold> issues first — do not bury them.</Li>
|
|
139
57
|
<Li>One finding per comment. No compound observations.</Li>
|
|
58
|
+
<Li>Use <Code>inline code</Code> when referencing identifiers.</Li>
|
|
140
59
|
</Ul>
|
|
141
|
-
|
|
142
|
-
<H2>Output format</H2>
|
|
143
|
-
<P>
|
|
144
|
-
Respond with a structured list. Each item: severity, location, finding.
|
|
145
|
-
</P>
|
|
146
60
|
</>
|
|
147
61
|
);
|
|
148
62
|
|
|
149
|
-
|
|
63
|
+
const prompt = render(<ReviewerPrompt repo="cockpit" />);
|
|
64
|
+
// "## Role\n\nYou are a precise code reviewer..."
|
|
150
65
|
```
|
|
151
66
|
|
|
152
|
-
|
|
67
|
+
`render()` returns a plain string. No virtual DOM, no React runtime. Same input, same string, every time.
|
|
68
|
+
|
|
69
|
+
---
|
|
153
70
|
|
|
154
|
-
|
|
155
|
-
## Role
|
|
71
|
+
## Context API
|
|
156
72
|
|
|
157
|
-
|
|
73
|
+
Avoids prop-drilling through shared fragment trees. Same shape as React — `createContext`, `useContext`, `Context.Provider` — synchronous, no rules-of-hooks.
|
|
158
74
|
|
|
159
|
-
|
|
75
|
+
```tsx
|
|
76
|
+
import { render, createContext, useContext, Ul, Li, Code } from "@theseus.run/jsx-md";
|
|
160
77
|
|
|
161
|
-
|
|
162
|
-
- Use `inline code` when referencing identifiers.
|
|
163
|
-
- One finding per comment. No compound observations.
|
|
78
|
+
const HarnessCtx = createContext<'opencode' | 'copilot'>('copilot');
|
|
164
79
|
|
|
165
|
-
|
|
80
|
+
const StepsSection = () => {
|
|
81
|
+
const harness = useContext(HarnessCtx);
|
|
82
|
+
return (
|
|
83
|
+
<Ul>
|
|
84
|
+
<Li>Always verify output before claiming done.</Li>
|
|
85
|
+
{harness === 'opencode' && <Li>Use <Code>task()</Code> for multi-step subtasks.</Li>}
|
|
86
|
+
</Ul>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
166
89
|
|
|
167
|
-
|
|
90
|
+
// Wire it once at the root — no prop threading:
|
|
91
|
+
const prompt = render(
|
|
92
|
+
<HarnessCtx.Provider value="opencode">
|
|
93
|
+
<StepsSection />
|
|
94
|
+
</HarnessCtx.Provider>
|
|
95
|
+
);
|
|
168
96
|
```
|
|
169
97
|
|
|
170
|
-
|
|
98
|
+
`useContext` returns the default when called outside a Provider. Providers nest — innermost wins.
|
|
171
99
|
|
|
172
|
-
|
|
100
|
+
---
|
|
173
101
|
|
|
174
|
-
|
|
102
|
+
## XML intrinsics
|
|
175
103
|
|
|
176
|
-
Any lowercase JSX tag
|
|
104
|
+
Any lowercase JSX tag renders as an XML block. No imports, no registration:
|
|
177
105
|
|
|
178
106
|
```tsx
|
|
179
107
|
const ReviewerPrompt = ({ repo, examples }: Props) => (
|
|
@@ -185,13 +113,6 @@ const ReviewerPrompt = ({ repo, examples }: Props) => (
|
|
|
185
113
|
<instructions>
|
|
186
114
|
<H2>Role</H2>
|
|
187
115
|
<P>You are a precise code reviewer. Find bugs, not style issues.</P>
|
|
188
|
-
|
|
189
|
-
<H2>Rules</H2>
|
|
190
|
-
<Ul>
|
|
191
|
-
<Li>Flag <Bold>P0</Bold> issues first — do not bury them.</Li>
|
|
192
|
-
<Li>One finding per comment. No compound observations.</Li>
|
|
193
|
-
<Li>Use <Code>inline code</Code> when referencing identifiers.</Li>
|
|
194
|
-
</Ul>
|
|
195
116
|
</instructions>
|
|
196
117
|
|
|
197
118
|
{examples.length > 0 && (
|
|
@@ -204,120 +125,73 @@ const ReviewerPrompt = ({ repo, examples }: Props) => (
|
|
|
204
125
|
</examples>
|
|
205
126
|
)}
|
|
206
127
|
</>
|
|
207
|
-
)
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
Output:
|
|
211
|
-
|
|
212
|
-
```
|
|
213
|
-
<context>
|
|
214
|
-
Repository: cockpit. Language: TypeScript. Package manager: bun.
|
|
215
|
-
</context>
|
|
216
|
-
|
|
217
|
-
<instructions>
|
|
218
|
-
## Role
|
|
219
|
-
|
|
220
|
-
You are a precise code reviewer. Find bugs, not style issues.
|
|
221
|
-
|
|
222
|
-
## Rules
|
|
223
|
-
|
|
224
|
-
- Flag **P0** issues first — do not bury them.
|
|
225
|
-
- One finding per comment. No compound observations.
|
|
226
|
-
- Use `inline code` when referencing identifiers.
|
|
227
|
-
</instructions>
|
|
228
|
-
|
|
229
|
-
<examples>
|
|
230
|
-
<example index="1">
|
|
231
|
-
... your example content ...
|
|
232
|
-
</example>
|
|
233
|
-
</examples>
|
|
128
|
+
);
|
|
234
129
|
```
|
|
235
130
|
|
|
236
|
-
Attributes are typed
|
|
131
|
+
Attributes are typed — `index={1}` serializes to `index="1"`. Boolean `true` renders bare, `false`/`null`/`undefined` are omitted. Empty tags self-close.
|
|
237
132
|
|
|
238
133
|
---
|
|
239
134
|
|
|
240
135
|
## Primitives
|
|
241
136
|
|
|
242
|
-
| Component |
|
|
243
|
-
|
|
137
|
+
| Component | Output |
|
|
138
|
+
|---|---|
|
|
244
139
|
| `H1`–`H6` | `#`–`######` headings |
|
|
245
140
|
| `P` | Paragraph (blank line separated) |
|
|
246
141
|
| `Hr` | `---` horizontal rule |
|
|
247
|
-
| `Codeblock` | Fenced code block
|
|
142
|
+
| `Codeblock` | Fenced code block (`lang` prop optional) |
|
|
248
143
|
| `Blockquote` | `>` blockquote |
|
|
249
144
|
| `Ul` | Unordered list |
|
|
250
|
-
| `Ol` | Ordered list |
|
|
251
|
-
| `Li` | List item (supports
|
|
145
|
+
| `Ol` | Ordered list (auto-numbered, nesting supported) |
|
|
146
|
+
| `Li` | List item (supports nested `Ul` or `Ol` inside `Li`) |
|
|
252
147
|
| `TaskList` | Task list container |
|
|
253
148
|
| `Task` | `- [ ]` / `- [x]` task item (`done` prop) |
|
|
254
149
|
| `Table` | Markdown table |
|
|
255
150
|
| `Tr` | Table row |
|
|
256
|
-
| `Th` | Table header cell |
|
|
151
|
+
| `Th` | Table header cell (`align`: `left`, `center`, `right`) |
|
|
257
152
|
| `Td` | Table data cell |
|
|
258
153
|
| `Bold` | `**bold**` |
|
|
259
154
|
| `Code` | `` `inline code` `` |
|
|
260
155
|
| `Italic` | `*italic*` |
|
|
261
156
|
| `Strikethrough` | `~~strikethrough~~` |
|
|
157
|
+
| `Br` | Hard line break (` \n` — two trailing spaces) |
|
|
158
|
+
| `Sup` | `<sup>content</sup>` superscript |
|
|
159
|
+
| `Sub` | `<sub>content</sub>` subscript |
|
|
160
|
+
| `Kbd` | `<kbd>content</kbd>` keyboard key |
|
|
161
|
+
| `Escape` | Escapes CommonMark metacharacters in children |
|
|
262
162
|
| `Link` | `[text](url)` |
|
|
263
163
|
| `Img` | `` |
|
|
264
|
-
| `Md` | Raw
|
|
265
|
-
| `HtmlComment` | `<!-- comment -->`
|
|
164
|
+
| `Md` | Raw Markdown passthrough — renders verbatim, no transformation |
|
|
165
|
+
| `HtmlComment` | `<!-- comment -->` — invisible to most renderers, useful for LLM-only instructions |
|
|
266
166
|
| `Details` | `<details><summary>` collapsible block |
|
|
267
|
-
| `Callout` | GitHub-style admonition
|
|
167
|
+
| `Callout` | GitHub-style admonition (`type`: `note`, `tip`, `important`, `warning`, `caution`) |
|
|
268
168
|
|
|
269
169
|
---
|
|
270
170
|
|
|
271
|
-
##
|
|
272
|
-
|
|
273
|
-
**v0.1.0** — current. TypeScript source, Bun-first. Markdown primitives + XML intrinsic elements.
|
|
274
|
-
|
|
275
|
-
**v0.2.0** — two additions:
|
|
276
|
-
|
|
277
|
-
- _Context API (public)._ `DepthContext` already uses an internal context system for list nesting. v0.2.0 exposes this as a first-class public API. The pattern is React-familiar but synchronous and string-based.
|
|
171
|
+
## Utilities
|
|
278
172
|
|
|
279
|
-
|
|
173
|
+
### `escapeMarkdown(s: string): string`
|
|
280
174
|
|
|
281
|
-
|
|
282
|
-
// v0.1.0 — prop-drilling
|
|
283
|
-
const Prompt = ({ harness }: Props) => (
|
|
284
|
-
<>
|
|
285
|
-
<Instructions harness={harness} />
|
|
286
|
-
<Rules harness={harness} />
|
|
287
|
-
<Examples harness={harness} />
|
|
288
|
-
</>
|
|
289
|
-
)
|
|
290
|
-
```
|
|
175
|
+
Escapes all CommonMark ASCII punctuation metacharacters with a backslash so user-supplied strings are treated as literal text by any markdown renderer.
|
|
291
176
|
|
|
292
|
-
|
|
177
|
+
```tsx
|
|
178
|
+
import { escapeMarkdown, Escape, P } from "@theseus.run/jsx-md";
|
|
293
179
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const HarnessContext = createContext<'opencode' | 'copilot'>('copilot')
|
|
180
|
+
// Function form
|
|
181
|
+
const safe = escapeMarkdown(untrustedInput); // "**bold**" → "\\*\\*bold\\*\\*"
|
|
297
182
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
</HarnessContext.Provider>
|
|
302
|
-
)
|
|
183
|
+
// Component form — same result, composable in JSX
|
|
184
|
+
render(<P>File: <Escape>{untrustedFilename}</Escape></P>);
|
|
185
|
+
```
|
|
303
186
|
|
|
304
|
-
|
|
305
|
-
const Instructions = () => {
|
|
306
|
-
const harness = useContext(HarnessContext)
|
|
307
|
-
return harness === 'opencode'
|
|
308
|
-
? <P>Use task() for subtasks.</P>
|
|
309
|
-
: null
|
|
310
|
-
}
|
|
311
|
-
```
|
|
187
|
+
Escaped characters: `` \ ` * _ [ ] ( ) # + - . ! | ~ < > ``
|
|
312
188
|
|
|
313
|
-
|
|
189
|
+
`&` is intentionally not escaped — use `escapeHtmlContent` for HTML contexts.
|
|
314
190
|
|
|
315
191
|
---
|
|
316
192
|
|
|
317
193
|
## License
|
|
318
194
|
|
|
319
|
-
MIT — see [LICENSE](../../LICENSE).
|
|
320
|
-
|
|
321
|
-
Built by [Roman Dubinin](https://romanonthego.com).
|
|
195
|
+
MIT — see [LICENSE](../../LICENSE).
|
|
322
196
|
|
|
323
|
-
Part of [Theseus](https://theseus.run).
|
|
197
|
+
Built by [Roman Dubinin](https://romanonthego.dev). Part of [Theseus](https://theseus.run).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/jsx-runtime.ts
|
|
2
|
+
var Fragment = Symbol("Fragment");
|
|
3
|
+
function jsx(type, props) {
|
|
4
|
+
return { type, props };
|
|
5
|
+
}
|
|
6
|
+
var jsxs = jsx;
|
|
7
|
+
|
|
8
|
+
export { Fragment, jsx, jsxs };
|
|
9
|
+
|
|
10
|
+
//# debugId=A10E4D57EB3603AA64756E2164756E21
|
|
11
|
+
//# sourceMappingURL=index-8tdwjkh9.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/jsx-runtime.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Custom JSX-to-VNode runtime for agent markdown generation.\n *\n * Bun's JSX compilation with `jsxImportSource: \"@theseus.run/jsx-md\"` resolves\n * `jsx` and `jsxs` from `@theseus.run/jsx-md/jsx-runtime`. The factory builds\n * a VNode tree — no evaluation at construction time.\n *\n * Call `render(node)` from `./render.ts` to produce the final markdown string.\n * All markdown primitives live in primitives.tsx as named components.\n *\n * Fragment is a Symbol. render.ts imports this Symbol and handles it explicitly,\n * keeping the dependency one-way: render.ts → jsx-runtime.ts (no cycle).\n */\n\n// ---------------------------------------------------------------------------\n// VNode types\n// ---------------------------------------------------------------------------\n\n/**\n * The concrete runtime shape of a JSX element — what the `jsx()` factory always produces.\n *\n * Useful for structural inspection of a VNode tree (e.g. testing whether a node is an\n * element rather than a string, null, or array). The `isVNodeElement` predicate in\n * render.ts narrows to this type.\n *\n * @remarks **Do not use as a component return-type annotation.** TypeScript infers\n * `JSX.Element` (= `VNode`) as the return type of JSX expressions, not `VNodeElement`.\n * Annotating a component as `(): VNodeElement` causes TS2322 because `VNode` (the\n * inferred type) is not assignable to the narrower `VNodeElement`. Use `VNode` instead:\n * ```ts\n * // Wrong — TS2322\n * function MyComp(): VNodeElement { return <P>hi</P>; }\n * // Correct\n * function MyComp(): VNode { return <P>hi</P>; }\n * ```\n */\nexport type VNodeElement = {\n readonly type: Component | string | typeof Fragment;\n readonly props: Record<string, unknown>;\n};\n\nexport type VNode =\n | null\n | undefined\n | boolean\n | string\n | number\n | VNodeElement\n | ReadonlyArray<VNode>;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype Component<P = any> = (props: P) => VNode;\n\n// ---------------------------------------------------------------------------\n// JSX namespace\n// ---------------------------------------------------------------------------\n\n// eslint-disable-next-line @typescript-eslint/no-namespace\nexport namespace JSX {\n /**\n * JSX.Element is VNode so that components returning any valid VNode\n * (string, VNodeElement, Fragment, null, etc.) satisfy TypeScript's\n * component return-type check. The jsx() factory always produces\n * VNodeElement at runtime; the wider union is only for type-checking.\n */\n export type Element = VNode;\n\n // Catch-all — allows arbitrary lowercase XML tags as intrinsic elements.\n export interface IntrinsicElements {\n [tag: string]: { children?: VNode; [attr: string]: unknown };\n }\n\n export interface ElementChildrenAttribute {\n children: VNode;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Fragment symbol\n// ---------------------------------------------------------------------------\n\n/**\n * Fragment — a unique Symbol used as the `type` of JSX fragment VNodes.\n * render.ts detects this Symbol and renders children directly, with no wrapper.\n * Using a Symbol (rather than a function) eliminates the circular dependency\n * that previously required _render-registry.ts.\n */\nexport const Fragment = Symbol('Fragment');\n\n// ---------------------------------------------------------------------------\n// JSX factory\n// ---------------------------------------------------------------------------\n\n/**\n * JSX factory — called by Bun's compiled JSX. Builds VNode tree; no evaluation.\n *\n * `type` is a function component, a string tag name, or the Fragment symbol.\n * String tags are rendered as XML blocks by render.ts: `<tag attrs>\\ncontent\\n</tag>\\n`\n * (or self-closing when the inner content is empty).\n */\nexport function jsx(\n type: Component | string | typeof Fragment,\n props: Record<string, unknown>,\n): VNodeElement {\n return { type, props };\n}\n\n/** jsxs — same as jsx, used when there are multiple children (children is array) */\nexport const jsxs = jsx;\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";AAuFO,IAAM,WAAW,OAAO,UAAU;AAalC,SAAS,GAAG,CACjB,MACA,OACc;AAAA,EACd,OAAO,EAAE,MAAM,MAAM;AAAA;AAIhB,IAAM,OAAO;",
|
|
8
|
+
"debugId": "A10E4D57EB3603AA64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|